#include "fifo.h"
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include "misc.h"


struct fifo *fifo_new(size_t size) {
    struct fifo *f;

    if (size && (size & (size - 1)))
        size = roundup_pow_of_2(size);
    if (size < 2) {
        errno = EINVAL;
        return NULL;
    }

    if ((f = malloc(sizeof(*f) + size))) {
        f->size = size;
        f->data = (void *)f + sizeof(*f);
        fifo_reset(f);
    } else {
        errno = ENOMEM;
    }

    return f;
}

void fifo_del(struct fifo *f) {
    free(f);
}

size_t fifo_used(struct fifo *fifo) {
    return fifo->end - fifo->start;
}

size_t fifo_unused(struct fifo *fifo) {
    return fifo->size - fifo_used(fifo);
}

size_t fifo_is_empty(struct fifo *fifo) {
    return fifo_used(fifo) == 0;
}

size_t fifo_is_full(struct fifo *fifo) {
    return fifo_used(fifo) == fifo->size;
}

inline void fifo_reset(struct fifo *fifo) {
    fifo->start = 0;
    fifo->end = 0;
}

size_t fifo_write(struct fifo *fifo, const void *buf, size_t len) {
    size_t l = fifo_unused(fifo);
    if (len > l)
        len = l;

    size_t off = fifo->end & (fifo->size - 1);
    l = min(len, fifo->size - off);
    memcpy(fifo->data + off, buf, l);
    memcpy(fifo->data, buf + l, len - l);

    fifo->end += len;
    return len;
}

size_t fifo_peek(struct fifo *fifo, size_t off, void *buf, size_t len) {
    size_t l = fifo_used(fifo);
    if (off >= l)
        return 0;

    if (len > l)
        len = l;

    off = (fifo->start + off) & (fifo->size - 1);
    l = min(len, fifo->size - off);
    memcpy(buf, fifo->data + off, l);
    memcpy(buf + l, fifo->data, len - l);

    return len;
}

size_t fifo_read(struct fifo *fifo, void *buf, size_t len) {
    len = fifo_peek(fifo, 0, buf, len);
    fifo->start += len;
    return len;
}

static ssize_t read_fd(struct fifo *fifo, size_t off, int fd, size_t reqn) {
    ssize_t rspn;

    if ((rspn = read(fd, fifo->data + off, reqn)) < 0)
        if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
            return 0;

    if (rspn > 0)
        fifo->end += rspn;

    return rspn;
}

ssize_t fifo_write_from_fd(struct fifo *fifo, int fd) {
    size_t len = fifo_unused(fifo);
    if (len == 0)
        return 0;

    size_t off = fifo->end & (fifo->size - 1);
    size_t l = min(len, fifo->size - off);

    ssize_t readn;
    if (l && (readn = read_fd(fifo, off, fd, l)) != (ssize_t)l)
        return readn;
    if (len - l && (readn = read_fd(fifo, 0, fd, len - l)) != (ssize_t)(len - 1))
        return readn < 0 ? ((ssize_t)l ?: readn) : readn + (ssize_t)l;

    return len;
}

static ssize_t write_fd(struct fifo *fifo, size_t off, int fd, size_t len) {
    ssize_t rspn;

    if ((rspn = write(fd, fifo->data + off, len)) < 0)
        if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
            return 0;
    if (rspn > 0)
        fifo->start += rspn;

    return rspn;
}

ssize_t fifo_read_to_fd(struct fifo *fifo, int fd) {
    size_t len = fifo_used(fifo);

    size_t off = fifo->start & (fifo->size - 1);
    size_t l = min(len, fifo->size - off);

    ssize_t writen;
    if (l && (writen = write_fd(fifo, off, fd, l)) != (ssize_t) l)
        return writen;
    if (len - l && (writen = write_fd(fifo, 0, fd, len - l)) != (ssize_t) (len - l))
        return writen < 0 ? (ssize_t)l ?: writen : writen + (ssize_t)l;

    return len;
}
